/*****************************************************************************
 * Copyright (C) Stacy Curl. All rights reserved.                            *
 * ------------------------------------------------------------------------- *
 * The software in this package is published under the terms of the BSD      *
 * style license a copy of which has been included with this distribution in *
 * the LICENSE.txt file.                                                     *
 *****************************************************************************/
package picounit.features.parameter_name;

import moved.org.objectweb.asm.Label;
import moved.org.objectweb.asm.MethodVisitor;
import picounit.features.classloader.NullVisitor;

import java.util.LinkedList;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class MethodParameterNameMethodVisitor extends NullVisitor implements MethodVisitor {
	private static final String PRIMATIVE_TYPE_REGEX = "\\[*[ZBCDFIJSV]";
	private static final String OBJECT_TYPE_REGEX = "\\[*L[^;]*;";

	private static final Pattern PRIMATIVE_TYPE_PATTERN =
		Pattern.compile("^(" + PRIMATIVE_TYPE_REGEX + ")(.*)");
	private static final Pattern OBJECT_TYPE_PATTERN =
		Pattern.compile("^(" + OBJECT_TYPE_REGEX + ")(.*)");

	private final MethodParameterListener methodParameterListener;
	private final List<String> parameterNames = new LinkedList<String>();

	private int numParameters;
	private String methodName;
	private String className;

	public MethodParameterNameMethodVisitor(MethodParameterListener methodParameterListener) {
		this.methodParameterListener = methodParameterListener;
	}

	public MethodVisitor visit(String className, String methodName, String description) {
		this.className = className;
		this.methodName = methodName;
		this.numParameters = getNumParameters(description);
		this.parameterNames.clear();

		return this;
	}

	@Override
	public void visitLocalVariable(String name, String desc, String signature, Label start,
		Label end, int index) {

		if ("this".equals(name)) {
			return;
		}

		if (numParameters > 0) {
			addParameterName(name);
			numParameters-- ;
			if (numParameters == 0) {
				//TODO: Check access of method and ignore static and/or private methods
				methodParameterListener.methodEvent(className, methodName, parameterNames());
			}
		}
	}

	///////////////////////////////////////////////////////////////////////////////////////////////
	// Implementation
	///////////////////////////////////////////////////////////////////////////////////////////////

	private int getNumParameters(String signature) {
		String parametersString = signature.substring(1, signature.indexOf(')'));
		int numParameters = 0;

		while (parametersString.length() > 0) {
			String nextMatch = "";

			Matcher primativeMatcher = PRIMATIVE_TYPE_PATTERN.matcher(parametersString);
			if (primativeMatcher.matches()) {
				nextMatch = primativeMatcher.group(1);
			}

			Matcher objectMatcher = OBJECT_TYPE_PATTERN.matcher(parametersString);
			if (objectMatcher.matches()) {
				nextMatch = objectMatcher.group(1);
			}

			numParameters++ ;

			parametersString = parametersString.substring(nextMatch.length());
		}

		return numParameters;
	}

	private void addParameterName(String name) {
		parameterNames.add(name);
	}

	private String[] parameterNames() {
		return parameterNames.toArray(new String[parameterNames.size()]);
	}
}
